package server

import (
	"encoding/binary"
	"fmt"
	proto "github.com/golang/protobuf/proto"
	"reflect"
)

const ProtoVerNo uint8 = 1

type RpcResponseStatus uint8

const (
	RpcResponseStatusOk RpcResponseStatus = iota
	RpcResponseStatusServiceNotExists
	RpcResponseStatusParamIncorrect
	RpcResponseStatusServiceProcessError
	RpcResponseStatusTimeout
)

var rpcRsqpStatusErrString = map[RpcResponseStatus]string{
	RpcResponseStatusOk:                  "ok",
	RpcResponseStatusServiceNotExists:    "requested service does not exist.",
	RpcResponseStatusParamIncorrect:      "request parameter incorrect.",
	RpcResponseStatusServiceProcessError: "remote process err",
	RpcResponseStatusTimeout:             "process timed out",
}

type LogicPacketType uint8

const (
	LogicPacketRPC LogicPacketType = iota
	LogicPacketEventPush
)

func (e RpcResponseStatus) Error() string {
	if s, ok := rpcRsqpStatusErrString[e]; !ok {
		panic("Invalid err type.")
	} else {
		return s
	}
}

type RpcObj interface {
	GetTransactionID() uint16
	GetMessageData() []byte
	Encode() []byte
}

type rpcObjectBase struct {
	transactionID uint16
	messageData   []byte
}
type Response struct {
	statusCode RpcResponseStatus
	rpcObjectBase
}

type Request struct {
	rpcObjectBase
	serviceName string
}

type Push struct {
	rpcObjectBase
	seriesID uint8
	seqID    uint16
	messages []proto.Message
}

func (p *Push) GetMessageData() []byte {
	return p.messageData
}

// InitData fill the raw data for Push,  parse the series ID and seq ID.
// the messages is to be parsed late by the Decode function, since the message type is unknown.
func (p *Push) InitData(messageData []byte) {
	p.messageData = messageData
	offset := 0
	p.seriesID = uint8(messageData[offset])
	offset += 1
	p.seqID = binary.BigEndian.Uint16(messageData[offset : offset+2])
}

func (p *Push) GetSeriesID() uint8 {
	return p.seriesID
}

// GetTransactionID returns the seqID instead of the transaction ID.
func (p *Push) GetTransactionID() uint16 {
	return p.seqID
}

// Decode
// messageData including seriesID, seqID, message count and the raw data of the messages.
// messageType if messageType is nil, no messages will be decoded.
func (p *Push) Decode(messageType reflect.Type) error {
	if p.messageData == nil {
		return fmt.Errorf("message data not intialized!")
	}

	offset := 3
	mCount := binary.BigEndian.Uint16(p.messageData[offset : offset+2])
	offset += 2
	for n := mCount; n > 0; n-- {
		mLen := binary.BigEndian.Uint16(p.messageData[offset : offset+2])
		offset += 2
		reflect.New(messageType)

		m := reflect.New(messageType).Interface().(proto.Message)
		err := proto.Unmarshal(p.messageData[offset:offset+int(mLen)], m)
		if err != nil {
			return fmt.Errorf("failed to decode message: %v", err)
		}
		offset += int(mLen)
		p.messages = append(p.messages, m)
	}
	return nil
}

func (rsp *Response) GetMessageData() []byte {
	return rsp.messageData
}

func (rsp *Response) GetTransactionID() uint16 {
	return rsp.transactionID
}

func (rsp *Response) GetStatus() RpcResponseStatus {
	return rsp.statusCode
}

func (rsp *Response) Encode() []byte {
	data := make([]byte, 8+len(rsp.messageData))
	data[0] = ProtoVerNo<<3 + uint8(LogicPacketRPC)
	binary.BigEndian.PutUint32(data[1:5], uint32(3+len(rsp.messageData)))
	binary.BigEndian.PutUint16(data[5:7], rsp.transactionID)
	data[7] = byte(rsp.statusCode)
	copy(data[8:], rsp.messageData)
	return data
}

func (req *Request) GetMessageData() []byte {
	return req.messageData
}

func (req *Request) GetTransactionID() uint16 {
	return req.transactionID
}

func (req *Request) GetServiceName() string {
	return req.serviceName
}

func (req *Request) Encode() []byte {
	nl := len(req.serviceName)
	data := make([]byte, 1+4+2+1+nl+len(req.messageData))
	data[0] = ProtoVerNo<<3 + uint8(LogicPacketRPC)
	binary.BigEndian.PutUint32(data[1:5], uint32(len(data)-5))
	binary.BigEndian.PutUint16(data[5:7], req.transactionID)
	data[7] = byte(nl)
	copy(data[8:8+nl], []byte(req.serviceName))
	copy(data[8+nl:], req.messageData)
	return data
}

func (p *Push) Encode() []byte {
	mbLen := 0
	mLens := make([]int, len(p.messages))
	mData := make([][]byte, len(mLens))
	for i, m := range p.messages {
		d, _ := proto.Marshal(m)
		// including the "message length bits length" and message data length
		mbLen += 2 + len(d)
		mData[i] = d
		mLens[i] = len(d)
	}
	data := make([]byte, 1+4+1+2+2+mbLen)
	data[0] = ProtoVerNo<<3 + uint8(LogicPacketEventPush)
	binary.BigEndian.PutUint32(data[1:5], uint32(len(data)-5))
	data[5] = p.seriesID
	binary.BigEndian.PutUint16(data[6:8], p.seqID)
	offset := 8
	mCountB := make([]byte, 2)
	binary.BigEndian.PutUint16(mCountB, uint16(len(p.messages)))
	copy(data[offset:offset+2], mCountB)
	offset += 2
	for i, d := range mData {
		binary.BigEndian.PutUint16(data[offset:offset+2], uint16(mLens[i]))
		offset += 2
		copy(data[offset:offset+mLens[i]], d)
		offset += mLens[i]
	}
	return data
}

func (p *Push) GetMessages() []proto.Message {
	return p.messages
}

func NewResponse(transactionId uint16, status RpcResponseStatus, messageData []byte) *Response {
	return &Response{
		statusCode: status,
		rpcObjectBase: rpcObjectBase{
			transactionID: transactionId,
			messageData:   messageData,
		},
	}
}

func NewRequest(serviceName string, transactionId uint16, messageData []byte) *Request {
	return &Request{
		rpcObjectBase: rpcObjectBase{
			transactionID: transactionId,
			messageData:   messageData,
		},
		serviceName: serviceName,
	}
}
func NewPush(seriesID uint8, seqID uint16, messages []proto.Message) *Push {
	return &Push{
		seriesID: seriesID,
		messages: messages,
		seqID:    seqID,
	}
}
